<!-- GanttChart.vue -->
<template>
  <!-- 上方控件区：日期选择 + 新建按钮 -->
  <div class="controls">
    <a-date-picker
      v-model:value="startTime"
      show-time
      format="YYYY-MM-DD HH:mm"
      @change="onDateChange"
      :disabled-date="disabledBeforeEarliest"
    />
    <a-button type="primary" @click="openNew">新建任务</a-button>
  </div>

  <div class="ganttChart">
    <!-- Gantt 主视图容器 -->
    <div class="gantt-container" ref="ganttContainerRef">
      <ul class="itemList scrollBar" :style="{ width: `${fullTimelineWidth + labelWidth}px` }">
        <!-- 表头：三层时间轴 -->
        <li class="item header" :style="{ width: `${fullTimelineWidth}px` }">
          <div class="label"></div>
          <div class="dates">
            <!-- 周 -->
            <div class="week-row">
              <div
                class="week-cell"
                v-for="(week, idx) in visibleWeeks"
                :key="week.label + '-' + idx"
                :style="{ width: `${week.span * currentWidth}px` }"
              >
                {{ week.label }}
              </div>
            </div>
            <!-- 天 -->
            <div class="day-row">
              <div
                class="day-cell"
                v-for="(day, idx) in visibleDays"
                :key="day.label + '-' + idx"
                :style="{ width: `${day.span * currentWidth}px` }"
              >
                {{ day.label }}
              </div>
            </div>
            <!-- 小时 -->
            <div class="hour-row">
              <div
                class="hour-cell"
                v-for="(cell, idx) in visibleHourCells"
                :key="cell.label + '-' + cell.span + '-' + idx"
                :style="{ width: `${cell.span * currentWidth}px` }"
              >
                {{ cell.label }}
              </div>
            </div>
          </div>
        </li>

        <!-- 任务行：每行一个 label + TaskBar -->
        <li
          class="item itemData"
          v-for="(t, idx) in todos"
          :key="t.id"
          :style="{ width: `${fullTimelineWidth}px` }"
        >
          <div class="label">{{ t.taskName }}</div>
          <TaskBar
            :task="t"
            :rowIndex="idx"
            :unitWidth="currentWidth"
            :viewStart="startTime ?? minT"
            @edit="onEdit"
            @delete="onDelete"
          />
        </li>
      </ul>


    </div>
    <!-- 缩放滑块 -->
    <div class="slider-container">
        <a-slider
          :min="minZoomPercent"
          :max="maxZoomPercent"
          v-model:value="zoomPercent"
          @input="onZoomChange"
          :draggableTrack="false"
          :tooltipOpen="false"
        />
      </div>
    <!-- 新建/编辑弹窗 -->
    <TaskEditDialog
      :visible="dialogVisible"
      @update:visible="dialogVisible = $event"
      :task="editingTask"
      @save="onSave"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import dayjs, { Dayjs } from 'dayjs'
import isoWeek from 'dayjs/plugin/isoWeek'
import { useTodoStore, type TodoItem } from '@/stores/todo'
import TaskEditDialog from '@/components/tools/TaskEditDialog.vue'
import TaskBar from '@/components/tools/TaskBar.vue'

dayjs.extend(isoWeek)
//
// —— 0. 全局引用 & store ——
//
const todoStore = useTodoStore()
const todos = computed(() => todoStore.todos)

//
// —— 1. 容器 & 滚动引用 ——
//
const ganttContainerRef = ref<HTMLElement | null>(null)
const listEl = ref<HTMLElement | null>(null)
const containerWidth = ref(0)

// ========== 2. 每「小时」的基准宽度，以及滑块最大值 ==========
const baseWidth = 40 // 基准 1 小时对应 40px
const sliderMax = ref(2000) // 滑块最大值，100 表示【完整范围】
const labelWidth = 120 // 左侧 label 列宽度
// —— 滚动 ——
const bodyWrapper = ref<HTMLElement | null>(null)

// ========== 3. 生成 hours[] ==========
// 新版：同时考虑 estimated 和 actual
const allHours = computed(() => {
  if (!todos.value.length) return []
  // 先从第一个任务里抽出四个时间点
  let minT = dayjs(todos.value[0].estimatedStartTime)
  let maxT = dayjs(todos.value[0].estimatedEndTime)
  // 比较的时候同时看 actual 开/结束 时间
  todos.value.forEach((t) => {
    const starts = [
      dayjs(t.estimatedStartTime),
      dayjs(t.actualStartTime)
    ]
    const ends = [
      dayjs(t.estimatedEndTime),
      dayjs(t.actualEndTime)
    ]
    // 求这一条任务最早的开始、最晚的结束
    const taskMin = starts.reduce((a, b) => a.isBefore(b) ? a : b)
    const taskMax = ends.reduce((a, b) => a.isAfter(b) ? a : b)
    if (taskMin.isBefore(minT)) minT = taskMin
    if (taskMax.isAfter(maxT)) maxT = taskMax
  })

  // 对齐到整点
  minT = minT.startOf('hour')
  maxT = maxT.endOf('hour')

  // 按小时生成刻度
  const arr: string[] = []
  let cursor = minT.clone()
  while (cursor.isBefore(maxT) || cursor.isSame(maxT, 'hour')) {
    arr.push(cursor.format('YYYY-MM-DD HH:mm'))
    cursor = cursor.add(1, 'hour')
  }
  return arr
})


const minT = computed(() => {
  const h = allHours.value
  return h.length ? dayjs(h[0], 'YYYY-MM-DD HH:mm') : dayjs().startOf('hour')
})

// 默认初始值为“最早那一小时”和“最末那一小时”
const startTime = ref<Dayjs | null>(null)
// 禁用 DatePicker：只能选 [earliestDateTime, latestDateTime] 范围内
const visibleHours = computed(() => allHours.value)

const visibleDays = computed(() => {
  const arr: { label: string; span: number }[] = []
  const vh = visibleHours.value
  if (!vh.length) return arr
  let ptr = vh[0].slice(0,10), cnt=0
  vh.forEach(h => {
    const d = h.slice(0,10)
    if (d === ptr) cnt++
    else {
      arr.push({ label: ptr.slice(5), span: cnt })
      ptr = d; cnt = 1
    }
  })
  arr.push({ label: ptr.slice(5), span: cnt })
  return arr
})

const visibleWeeks = computed(() => {
  const tmp: { weekLabel:string; span:number }[] = []
  visibleHours.value.forEach(h => {
    const d   = dayjs(h,'YYYY-MM-DD HH:mm')
    const lbl = `${d.isoWeekYear()}-W${d.isoWeek()}`
    const idx = tmp.findIndex(x => x.weekLabel === lbl)
    if (idx === -1) tmp.push({ weekLabel: lbl, span: 1 })
    else tmp[idx].span++
  })
  return tmp.map(w => ({ label: w.weekLabel, span: w.span }))
})

// ========== 5. 缩放滑块状态：range = [h1, h2] ==========
const range = ref<[number, number]>([0, 0])

// ========== 6. 计算缩放因子 & 当前每小时宽度 ==========
// 宽度计算
const zoomPercent = ref(100)  // 初始 50%
const minZoomPercent = 10    // 最小 10%
const maxZoomPercent = 200   // 最大 200%
const zoomFactor   = computed(() => zoomPercent.value / 100)

// —— 新增：最小缩放因子 = containerWidth / (hours.length * baseWidth) ——
const currentWidth = computed(() => baseWidth * zoomFactor.value)
const fullTimelineWidth = computed(() => allHours.value.length * currentWidth.value)

//
// —— 7. 生命周期 & 侦听 ——
//
onMounted(() => {
  todoStore.initTodos()
  if (ganttContainerRef.value) {
    containerWidth.value = ganttContainerRef.value.clientWidth
  }
  window.addEventListener('resize', () => {
    if (ganttContainerRef.value) {
      containerWidth.value = ganttContainerRef.value.clientWidth
    }
  })
  listEl.value = document.querySelector('.itemList')
})

watch([startTime], async ([dt]) => {
  if (!dt || !listEl.value) return
  await nextTick()
  const idx = allHours.value.indexOf(dt.format('YYYY-MM-DD HH:mm'))
  if (idx >= 0) listEl.value.scrollTo({ left: idx * currentWidth.value, behavior: 'smooth' })
})
watch(allHours, hours => {
  sliderMax.value = hours.length - 1
  range.value = [0, hours.length - 1]
})

// —— 在此处插入：根据 currentWidth 动态生成 hourCells ——
//    hourCells 中每项 = { label: string, span: number }
//    span 表示跨多少小时
const visibleHourCells = computed<{ label: string; span: number }[]>(() => {
  const w = currentWidth.value
  const arr: { label: string; span: number }[] = []
  const hs = allHours.value
  const total = hs.length

  if (w >= baseWidth) {
    // 每小时一格
    for (let i = 0; i < total; i++) {
      arr.push({ label: hs[i].slice(-5), span: 1 })
    }
  } else if (w * 1.5 >= baseWidth) {
    // 每 2 小时一格
    for (let i = 0; i < total; i += 2) {
      const startLabel = hs[i].slice(-5)
      // 如果不满两小时也做 1 个格子
      arr.push({ label: startLabel, span: Math.min(2, total - i) })
    }
  } else if (w * 4 >= baseWidth) {
    // 每 4 小时一格
    for (let i = 0; i < total; i += 4) {
      const startLabel = hs[i].slice(-5)
      arr.push({ label: startLabel, span: Math.min(4, total - i) })
    }
  } else if (w * 8 >= baseWidth) {
    // 每 8 小时一格
    for (let i = 0; i < total; i += 8) {
      const startLabel = hs[i].slice(-5)
      arr.push({ label: startLabel, span: Math.min(8, total - i) })
    }
  } else if (w * 12 >= baseWidth) {
    for (let i = 0; i < total; i += 12) {
      /* 每 12 小时一格 */
    }
  } else if (w * 24 >= baseWidth) {
    for (let i = 0; i < total; i += 24) {
      /* 每 24 小时一格 */
    }
  } else {
    // 如果依然过窄，可考虑在三层时间轴中隐藏某些细节，如只显示整日或周等
  }
  return arr
})

// ========== 7. ==========
const viewStart = ref<Dayjs | null>(null)
const viewEnd = ref<Dayjs | null>(null)

function recalcRange() {
  if (!todos.value.length) {
    viewStart.value = viewEnd.value = null
    return
  }
  let minT = dayjs(todos.value[0].estimatedStartTime)
  let maxT = dayjs(todos.value[0].estimatedEndTime)
  todos.value.forEach((t) => {
    const s = dayjs(t.estimatedStartTime),
      e = dayjs(t.estimatedEndTime)
    if (s.isBefore(minT)) minT = s
    if (e.isAfter(maxT)) maxT = e
  })
  viewStart.value = minT.startOf('hour')
  viewEnd.value = maxT.endOf('hour')
}
// —— 8. 控件回调 ——
//
function onDateChange(dt: Dayjs) {
  startTime.value = dt
  nextTick(() => {
    const key = dt.format('YYYY-MM-DD HH:mm')
    const idx = allHours.value.indexOf(key)
    if (idx >= 0 && listEl.value) {
      listEl.value.scrollTo({
        left: idx * currentWidth.value,
        behavior: 'smooth'
      })
    }
  })
}
function disabledBeforeEarliest(current: Dayjs) {
  return current.isBefore(minT.value, 'minute')
}
function onZoomChange() {
}
// —— 9. 新建 / 编辑 / 保存 ——
//
const dialogVisible = ref(false)
const editingTask = ref<TodoItem | null>(null)

function openNew() {
  editingTask.value = null
  dialogVisible.value = true
}
function onEdit(t: TodoItem) {
  editingTask.value = { ...t }
  dialogVisible.value = true
}
function onSave(t: TodoItem) {
  // 1. 更新 store
  if (todoStore.todos.some((x) => x.id === t.id)) todoStore.update(t)
  else todoStore.add(t)

  // 2. 强制刷新 viewStart/viewEnd
  recalcRange()

  // 3. 滚到新条目
  nextTick(() => {
    const key = dayjs(t.estimatedStartTime).format('YYYY-MM-DD HH:mm')
    const idx = allHours.value.indexOf(key)
    if (idx >= 0 && bodyWrapper.value) {
      bodyWrapper.value.scrollTo({ left: idx * currentWidth.value, behavior: 'smooth' })
    }
  })

  dialogVisible.value = false
}
function onDelete(task: TodoItem) {
  // 二次确认已在 Popconfirm 里做过，直接删：
  todoStore.remove(task.id)
}
</script>

<style scoped lang="less">
.ganttChart {
  display: flex;
  flex-direction: column;
  padding: 16px;
  background: #fff;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  height: 100%;

  /* ====== 1. 缩放滑块 ====== */
  .slider-container {
    margin-bottom: 12px;
    padding: 5px 100px 0 100px; /* 留出标签列宽度 */
    box-sizing: border-box;
    .ant-slider {
      margin: 0;
    }
    .slider-labels {
      text-align: right;
      font-size: 12px;
      margin-top: 4px;
      color: #555;
    }
  }

  /* ====== 2. Gantt 主区域 ====== */
  .gantt-container {
    flex: 1;
    /* 保留 overflow hidden，滚动交给 itemList */
    overflow-x: auto; /* 横向滚动 */
    overflow-y: auto; /* 纵向滚动 */
    border: #a8a8a8 solid 1px;
    border-radius: 5px;
  }

  .itemList {
    position: relative;
    height: 100%;
    overflow: visible;
    margin: 0;
    padding: 0;
    list-style: none;


    &::-webkit-scrollbar {
      width: 8px;
      height: 16px;
    }
    &::-webkit-scrollbar-track {
      background: transparent;
    }
    &::-webkit-scrollbar-thumb {
      background-color: #bfbfbf;
      border-radius: 5px;
      border: 1px solid #f1f1f1;
    }
    &::-webkit-scrollbar-thumb:hover {
      background-color: #a8a8a8;
    }
  }
  /* ① 表头行垂直方向固定 */
.itemList > .header {
  position: sticky;
  top: 0;
  z-index: 10;
}

/* ② 表头左列在横向滚动时依旧固定 */
.itemList > .header .label {
  position: sticky;
  left: 0;
  z-index: 11;
}
  /* ====== 表头：三层 时间轴 ====== */
  .header {
    position: sticky;
    top: 0;
    background-color: #f5f5f5;
    z-index: 10;
    display: grid;
    grid-template-columns: var(--labelWidth) 1fr;
    border-bottom: 1px solid #ccc;
    height: 72px;
    --labelWidth: 120px;

    .label {
      position: sticky;
      left: 0;
      width: var(--labelWidth);
      background-color: #f5f5f5;
      z-index: 15;
      border-right: 1px solid #ccc;
      /* 留空内容即可，占位 */
    }

    .dates {
      display: flex;
      flex-direction: column;
      /* 无需设置宽度，交给 li.header 上的宽度控制 */
    }

    /* 第一行：周 */
    .week-row {
      display: flex;
      height: 24px;
      background-color: #d9eaff;
      border-bottom: 1px solid #ccc;

      .week-cell {
        box-sizing: border-box;
        border-left: 1px solid #ccc;
        text-align: center;
        line-height: 24px;
        font-size: 12px;
        background-color: #d9eaff;
        color: #333;
        &:first-child {
          border-left: none;
        }
      }
    }

    /* 第二行：天 */
    .day-row {
      display: flex;
      height: 24px;
      background-color: #e6f7ff;
      border-bottom: 1px solid #ccc;

      .day-cell {
        box-sizing: border-box;
        border-left: 1px solid #ccc;
        text-align: center;
        line-height: 24px;
        font-size: 12px;
        background-color: #e6f7ff;
        color: #333;
        &:first-child {
          border-left: none;
        }
      }
    }

    /* 第三行：小时 */
    .hour-row {
      display: flex;
      height: 24px;
      background-color: #fff;
      border-bottom: 1px solid #ccc;

      .hour-cell {
        box-sizing: border-box;
        border-left: 1px solid #eee;
        text-align: center;
        line-height: 24px;
        font-size: 10px;
        background-color: #fff;
        color: #666;
        &:first-child {
          border-left: none;
        }
      }
    }
  }

  /* ====== 任务行 ====== */
  .item {
    display: grid;
    grid-template-columns: var(--labelWidth) 1fr;
    box-sizing: border-box;
    border-bottom: 1px solid #eee;
    min-height: 36px;
    position: relative;

    .label {
      position: sticky;
      left: 0;
      width: 120px;
      background-color: #fff;
      z-index: 5;
      text-align: center;
      font-size: 12px;
      padding: 4px 0;
      border-right: 1px solid #eee;
      height: 100%;
    }

    .speedOfProgress {
      position: absolute;
      height: 16px;
      // margin-top: 8px;
      background-color: #b9d8fb;
      border-radius: 4px;
      overflow: hidden;
      cursor: pointer;
      font-size: 10px;
      color: #000;
      line-height: 16px;

      .progress {
        position: absolute;
        height: 100%;
        background-color: #276cff;
      }
      &.estimated {
        background-color: rgba(39, 108, 255, 0.5); /* 蓝半透明 */
        top: calc(50% - 15px); /* 向下偏移，靠近上方 */
      }
      &.actual {
        background-color: rgba(255, 165, 0, 0.5); /* 橙半透明 */
        top: calc(50%); /* 再向下偏移一点，错开显示 */
      }
    }
  }

  .itemData {
    position: relative;
  }
}
</style>
